package com.haibara.codesubmiteval.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.haibara.codesubmiteval.common.ErrorCode;
import com.haibara.codesubmiteval.constant.CommonConstant;
import com.haibara.codesubmiteval.constant.RedisKeyConstant;
import com.haibara.codesubmiteval.exception.ThrowUtils;
import com.haibara.codesubmiteval.mapper.QuestionCaseMapper;
import com.haibara.codesubmiteval.mapper.QuestionMapper;
import com.haibara.codesubmiteval.mapper.QuestionSubmitMapper;
import com.haibara.codesubmiteval.mapper.UserMapper;
import com.haibara.codesubmiteval.model.dto.question.QuestionAddRequest;
import com.haibara.codesubmiteval.model.dto.question.QuestionEditRequest;
import com.haibara.codesubmiteval.model.dto.question.QuestionQueryRequest;
import com.haibara.codesubmiteval.model.entity.Question;
import com.haibara.codesubmiteval.model.entity.QuestionCase;
import com.haibara.codesubmiteval.model.entity.QuestionSubmit;
import com.haibara.codesubmiteval.model.entity.User;
import com.haibara.codesubmiteval.model.vo.QuestionVO;
import com.haibara.codesubmiteval.service.QuestionService;
import com.haibara.codesubmiteval.utils.RedisRecoveryUtil;
import com.haibara.codesubmiteval.utils.SqlUtils;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


/**
 * @author AiChan
 * @description 针对表【question(题目)】的数据库操作Service实现
 * @createDate 2023-12-20 13:17:24
 */
@Service
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question>
        implements QuestionService {
    @Resource
    private UserMapper userMapper;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedisRecoveryUtil redisRecoveryUtil;
    @Resource
    private QuestionCaseMapper questionCaseMapper;
    @Resource
    private QuestionSubmitMapper questionSubmitMapper;
    @Resource
    private Top10Service top10Service;

    @Override
    public Long addQuestion(QuestionAddRequest questionAddRequest) {
        List<String> tags = questionAddRequest.getTags();
        Question question = new Question();
        BeanUtil.copyProperties(questionAddRequest, question);
        if (tags != null && !tags.isEmpty()) {
            String tagsJsonStr = JSONUtil.toJsonStr(tags);
            question.setTags(tagsJsonStr);
        }
        question.setRatio(0);
        this.save(question);
        // Redis中总题目数+1
        stringRedisTemplate.opsForValue().increment(RedisKeyConstant.questionNumber, 1);
        // 删除Redis中可能存在的Question空值
        stringRedisTemplate.delete(RedisKeyConstant.getCompleteKey(RedisKeyConstant.questionKey, question.getId().toString()));
        // 修改题目总分
        redisRecoveryUtil.recoveryTotalQuestionScore();
        return question.getId();
    }

    @Override
    public void updateQuestion(QuestionEditRequest questionEditRequest) {
        Question question = new Question();
        BeanUtil.copyProperties(questionEditRequest, question);
        List<String> tags = questionEditRequest.getTags();
        if (tags != null && !tags.isEmpty()) {
            String tagsJsonStr = JSONUtil.toJsonStr(tags);
            question.setTags(tagsJsonStr);
        }
        Question originQuestion = this.getById(question.getId());
        this.updateById(question);
        Integer score = questionEditRequest.getScore();
        if (score != null && !score.equals(originQuestion.getScore())) {
            // 处理修改题目分值连带的用户分数修改
            updateUserScore(question.getId(), score - originQuestion.getScore());
            // 修改题目总分
            redisRecoveryUtil.recoveryTotalQuestionScore();
        }
        // 删除Question缓存
        stringRedisTemplate.delete(RedisKeyConstant.getCompleteKey(RedisKeyConstant.questionKey, question.getId().toString()));
    }

    @Override
    @Transactional
    public void deleteQuestion(Long questionId) {
        // 获取分数
        Question question = this.getById(questionId);
        Integer score = question.getScore();
        this.removeById(questionId);
        // Redis中题目总数-1
        stringRedisTemplate.opsForValue().increment(RedisKeyConstant.questionNumber, -1);
        // 处理题目分数带来的影响
        if (score != 0) {
            updateUserScore(questionId, -score);
            redisRecoveryUtil.recoveryTotalQuestionScore();
        }
        stringRedisTemplate.delete(RedisKeyConstant.getCompleteKey(RedisKeyConstant.questionKey, question.getId().toString()));
        // 删除测试用例
        questionCaseMapper.delete(
                new LambdaQueryWrapper<QuestionCase>()
                        .eq(QuestionCase::getQuestionId, questionId)
        );
        // 删除用户通过id集合中该题目id
        Set<Long> userIds = findEffectAcceptedQuestionIds(questionId);
        if (userIds != null && !userIds.isEmpty()) {
            // 删除Redis中通过题目id集合中该题目id的信息
            stringRedisTemplate.executePipelined(
                    (RedisCallback<Object>) connection -> {
                        for (Long key : userIds) {
                            // 手动序列化 Key 和 Value（StringRedisTemplate 默认使用 UTF-8）
                            byte[] rawKey = RedisKeyConstant.getCompleteKey(
                                    RedisKeyConstant.userAcceptedQuestionIdsSet,
                                    key.toString()
                            ).getBytes(StandardCharsets.UTF_8);
                            byte[] rawValue = questionId.toString().getBytes(StandardCharsets.UTF_8);
                            connection.setCommands().sRem(rawKey, rawValue);
                        }
                        return null;
                    }
            );
            // 处理数据库
            // 获取各个用户的通过题目id集合
            List<Object> allAcceptedQuestionIds = stringRedisTemplate.executePipelined(
                    (RedisCallback<Object>) connection -> {
                        for (Long userId : userIds) {
                            String key = RedisKeyConstant.getCompleteKey(
                                    RedisKeyConstant.userAcceptedQuestionIdsSet,
                                    userId.toString()
                            );
                            connection.setCommands().sMembers(key.getBytes(StandardCharsets.UTF_8));
                        }
                        return null;
                    }
            );
            List<User> batchUser = new ArrayList<>();
            int index = 0;
            for (Long userId : userIds) {
                User user = new User();
                user.setId(userId);
                user.setAcceptedQuestionIds(
                        JSONUtil.toJsonStr(allAcceptedQuestionIds.get(index++)).replaceAll("\"", "")
                );
                batchUser.add(user);
            }
            // 批量更新
            userMapper.updateBatch(batchUser);
        }
        // 删除题目提交记录中关于该题目的记录
        questionSubmitMapper.delete(
                new LambdaQueryWrapper<QuestionSubmit>()
                        .eq(QuestionSubmit::getQuestionId, questionId)
        );
    }

    public Set<Long> findEffectAcceptedQuestionIds(Long questionId) {
        // 获取受影响的用户id
        String pattern = RedisKeyConstant.getCompleteKey(RedisKeyConstant.userAcceptedQuestionIdsSet, "*");
        Set<String> keys = new HashSet<>();
        Set<Long> userIds = new HashSet<>();

        // 1. 收集所有 UserQuestionSet:{userId} 的 Key
        try (Cursor<String> cursor = stringRedisTemplate.scan(
                ScanOptions.scanOptions().match(pattern).build()
        )) {
            cursor.forEachRemaining(keys::add);
        }

        // 2. 使用 Pipeline 批量执行 SISMEMBER（通过 RedisTemplate 高级 API）
        List<Object> results = stringRedisTemplate.executePipelined(
                (RedisCallback<Object>) connection -> {
                    for (String key : keys) {
                        // 手动序列化 Key 和 Value（StringRedisTemplate 默认使用 UTF-8）
                        byte[] rawKey = key.getBytes(StandardCharsets.UTF_8);
                        byte[] rawValue = questionId.toString().getBytes(StandardCharsets.UTF_8);
                        connection.setCommands().sIsMember(rawKey, rawValue);
                    }
                    return null;
                }
        );

        // 3. 提取匹配的 userId
        int index = 0;
        for (String key : keys) {
            if (Boolean.TRUE.equals(results.get(index++))) {
                userIds.add(Long.parseLong(key.split(":")[1]));
            }
        }
        return userIds;
    }

    public void updateUserScore(Long questionId, int deltaScore) {
        // 获取受影响的用户id
        Set<Long> affectedUserId = findEffectAcceptedQuestionIds(questionId);
        // 处理排行榜
        // 获取普通用户ids
        if (affectedUserId.isEmpty()) {
            return;
        }
        List<Long> commonUserIds = userMapper.getCommonUserIdByIdList(affectedUserId);
        // 处理Redis榜单中连带的分数修改
        top10Service.batchIncrUserScore(commonUserIds, deltaScore);
        // 处理数据库中用户分数
        userMapper.batchUpdateScore(deltaScore, affectedUserId);
    }

    @Override
    public Page<Question> pageQueryQuestion(QuestionQueryRequest questionQueryRequest) {
        Long id = questionQueryRequest.getId();
        String title = questionQueryRequest.getTitle();
        List<String> tags = questionQueryRequest.getTags();
        Long userId = questionQueryRequest.getUserId();
        long current = questionQueryRequest.getCurrent();
        long pageSize = questionQueryRequest.getPageSize();
        String sortField = questionQueryRequest.getSortField();
        String sortOrder = questionQueryRequest.getSortOrder();
        // 构建查询条件
        LambdaQueryWrapper<Question> lqw = new LambdaQueryWrapper<>();
        // 过滤
        lqw.eq(id != null, Question::getId, id)
                .like(!StrUtil.isBlankIfStr(title), Question::getTitle, title)
                .eq(userId != null, Question::getUserId, userId);
        // 进行tags模糊匹配
        if (tags != null && !tags.isEmpty()) {
            for (String tag : tags) {
                lqw.like(!StrUtil.isBlankIfStr(tag), Question::getTags, "\"" + tag + "\"");
            }
        }
        // 排序
        if (SqlUtils.validSortField(sortField)) {
            // 根据创建时间排序
            lqw.orderBy("createTime".equals(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), Question::getCreateTime);
            // 根据更新时间排序
            lqw.orderBy("updateTime".equals(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), Question::getUpdateTime);
            // 根据题目id
            lqw.orderBy("id".equals(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), Question::getId);
            // 根据题目通过率排序
            lqw.orderBy("ratio".equals(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), Question::getRatio);
        }
        // 分页
        return this.page(new Page<>(current, pageSize), lqw);
    }

    @Override
    public Page<QuestionVO> pageQueryQuestionVO(QuestionQueryRequest questionQueryRequest) {
        Page<Question> questionPage = this.pageQueryQuestion(questionQueryRequest);
        Page<QuestionVO> result = new Page<>();
        BeanUtil.copyProperties(questionPage, result);
        result.setRecords(
                questionPage.getRecords().stream().map(
                        QuestionVO::getQuestionVOByQuestion
                ).toList()
        );
        return result;
    }

    @Override
    public QuestionVO getQuestionVO(Long id) {
        // 先尝试从Redis获取
        String questionStr = stringRedisTemplate.opsForValue().get(RedisKeyConstant.getCompleteKey(RedisKeyConstant.questionKey, id.toString()));
        if (questionStr == null || StrUtil.isBlankIfStr(questionStr)) {
            questionStr = redisRecoveryUtil.recoveryQuestion(id);
        }
        // 空值
        ThrowUtils.throwIf(RedisKeyConstant.NoneValue.equals(questionStr), ErrorCode.NOT_FOUND_ERROR, "题目不存在 : " + id);
        Question question = JSONUtil.toBean(questionStr, Question.class);
        return QuestionVO.getQuestionVOByQuestion(question);
    }
}




